<?php
/**
 * @package au2 
 * @author Jaroslaw Mezyk <j.mezyk@netcoffee.pl>
 * @copyright Jaroslaw Mezyk 2008
 * @license GPL 3
 */

require_once dirname(__FILE__) . '/variables_au2.class.php';

/**
 * Handles all template file processing
 *
 * @package au2
 * @license GPL 3
 * @author Jaroslaw Mezyk <j.mezyk@netcoffee.pl>
 * @copyright Jaroslaw Mezyk 2008
 */
class template_au2{
	/**
	 * path to template file
	 * 
	 * @var string
	 * @access private
	 */
	private $sourceFileName = ''; 


	/**
	 * path to target file
	 * 
	 * @var string
	 * @access private
	 */
	private $targetFileName = '';


	/**
	 * keeps unmodified source
	 * 
	 * @var string
	 * @access private
	 */
	private $originalSource = '';


	/**
	 * keeps current source (modified)
	 * 
	 * @var string
	 * @access private
	 */
	private $contents = '';


	/**
	 * keeps new line characters of the template file
	 * 
	 * @var string
	 * @access private
	 * @see openSourceFile()
	 */
	private $newLineCharacter = "\r\n";


	/**
	 * Keeps reference to global singleton object - variables_au2
	 * 
	 * @var variables_au2
	 * @access private
	 */
	private $vars;


	/**
	 * Keeps headers assigned to template. 
	 *
	 * Array of hashes. Each hash contains one header:
	 * <pre>
	 * 	array('name' => 'header name', 'value' => 'header value');
	 * </pre>
	 * 
	 * @var array
	 * @access private
	 * @see addHeader()
	 * @see getNextHeader()
	 */
	private $haHeaders = array();



	/**
	 * Keeps index of headers, so it is easy  to find position of headers by name
	 *
	 * hash of arrays. Every array contains indexes of headers with given name in
	 * haHeaders.
	 * egzample:
	 * if $this -> haHeaders is:
	 * <pre>
	 * array(
	 * 		array('name' => 'var', 'value' => 'a=8') // index: 0
	 * 	,	array('name' => 'var', 'value' => 'b=3') // index: 1
	 * 	,	array('name' => 'var', 'value' => 'c=9') // index: 2
	 * 	,	array('name' => 'target', 'value' => 'main.css') // index: 3
	 * )
	 * </pre>
	 * then $this -> ahHeadersIndex  is:
	 * <pre>
	 * array(
	 * 		'var' => array(0, 1, 2)
	 * 	,	'target' => array(3)
	 * )
	 * </pre>
	 * 
	 * @var array
	 * @access private
	 * @see addHeader()
	 * @see getNextHeaderByName()
	 */
	private $ahHeadersIndex = array();

	/**
	 * constructor
	 * 
	 * @param string $sFileName path to template file
	 * @access public
	 * @return void
	 * @throws Exception - if file can not be found or is not readable
	 */
	function __construct($sFileName){
/*{{{*/
		if(file_exists($sFileName) and is_file($sFileName) and is_readable($sFileName)){

			$this -> vars = variables_au2::singleton();

			$this -> sourceFileName = $sFileName;
			$this -> openSourceFile();

			$this -> saveTargetFile();
		}else{
			throw new Exception("Template file '{$sFileName}' not found or can not be read");
		}
/*}}}*/
	}

	/**
	 * reads source file and makes all processing
	 * 
	 * @access private
	 * @return void
	 */
	private function openSourceFile(){
/*{{{*/

		$sSource = file_get_contents($this -> sourceFileName);
		$this -> originalSource = $sSource;

		/**
		 * New line character detection  
		 * If file contains \r\n 
		 */
		if(strpos($sSource, "\r\n") !== false){
			// leave default newLineCharacter (windows style)
		}elseif(strpos($sSource, "\r") !== false){
			// (Apple style)
			$this -> newLineCharacter = "\r";
		}elseif(strpos($sSource, "\n") !== false){
			// Unix style
			$this -> newLineCharacter = "\n";
		}else{
			// no new line characters found. Leaving default value
		}




		/*
		 * If file begins with template headers marker then first part of file 
		 * (separated from second part with empty line) contains
		 * template headers. If so, we should extract headers from source.
		 */

		$sTemplateHeadersMaker = '[au::headers]';


		if(substr($sSource, 0, strlen($sTemplateHeadersMarker)) == $sTemplateHeadersMarker){
			list($sHeaders, $sSource) = explode($this -> newLineCharacter . $this -> newLineCharacter, $sSource, 2);

			$sHeaders = str_replace($sTemplateHeadersMarker, '', $sHeaders);

			$sHeaders = trim($sHeaders);
			$sSource = trim($sSource);
		}else{
			$sHeaders = '';
		}

		$this -> contents = $sSource;

		/**
		 * reading template headers 
		 */
		$saHeaders = explode($this -> newLineCharacter, $sHeaders);
		$iCnt = sizeof($saHeaders);
		for($x = 0; $x < $iCnt; $x++){
			list($sHeaderName, $sHeaderValue) = explode(':', $saHeaders[$x]);
			$this -> addHeader(trim($sHeaderName), trim($sHeaderValue));
		}


		/**
		 * Do all processing stuff  
		 */
		$this -> processTemplate();

/*}}}*/
	}


	/**
	 * adds header value to headers registry, and performs action specific to header
	 *
	 * Adds header to $haHeaders and $ahHeadersIndex
	 * Performs action specific to header
	 * 
	 * @param string $sHeaderName name of the header
	 * @param string $sHeaderValue value of the header
	 * @access public
	 * @return void
	 * @see haHeaders
	 * @see ahHeadersIndex
	 */
	public function addHeader($sHeaderName, $sHeaderValue){
/*{{{*/
		// add header to $this -> haHeaders and get it's index
		$iHeaderIndex = array_push($this -> haHeaders, array('name' => $sHeaderName, 'value' => $sHeaderValue)) - 1;

		// create array of indexes for given header name if not exists
		if(!isset($this -> ahHeadersIndex[$sHeaderName])){
			$this -> ahHeadersIndex[$sHeaderName] = array();
		}

		// add header's index to index
		$this -> ahHeadersIndex[$sHeaderName][] = $iHeaderIndex;


		/**
		 * Setting variable value using header 
		 * Header name is 'var' and header value is: variable_name=variable_value.
		 * We are using $this -> vars to manage all the variables
		 */
		if($sHeaderName == 'var'){
/*{{{*/
			list($sVariableName, $sVariableValue) = explode('=', $sHeaderValue, 2);
			$sVariableName = trim($sVariableName);
			$sVariableValue = trim($sVariableValue);

			$this -> vars -> setVar($sVariableName, $sVariableValue);
/*}}}*/
		}


		/**
		 * Setting output file  
		 */
		if($sHeaderName == 'output'){
/*{{{*/
			try{
				$this -> setTargetFile($sHeaderValue);
			}catch(Exception $e){
				// can not set output file
			}
/*}}}*/
		}
		
/*}}}*/
	}


	/**
	 * sets target file
	 * 
	 * @param string $sFile path to target file
	 * @access private
	 * @return void
	 * @throws Exception if file is not writable
	 */
	private function setTargetFile($sFile){
/*{{{*/
		// check if path is absolute
		$bAbsolute = ($sFile[1] == ':' or $sFile[1] == '/');

		if(!$bAbsolute){
			$sFile = dirname($this -> sourceFileName) . '/' . $sFile;
		}

		// check if path is directory path
		$bDirectory = (in_array(substr($sFile, -1), array('/', "\\")));


		if($bDirectory){
			$this -> setTargetFile($sFile . basename($this -> sourceFileName));

		}else{
			if(file_exists($sFile)){
				// file already exists
				if(is_writable($sFile)){
					$this -> targetFileName = $sFile;
				}else{
					throw new Exception("Target file '{$sFile}' is not writable");
				}
			}else{
				// file does not exist
				if(is_writable(dirname($sFile))){
					$this -> targetFileName = $sFile;
				}else{
					$sDir = dirname($sFile);
					throw new Exception("Target directory '{$sDir}' is not writable");
				}
			}

		}
/*}}}*/
	}



	/**
	 * Does all the processing stuff on template
	 * 
	 * @access private
	 * @return void
	 */
	private function processTemplate(){
/*{{{*/
		$this -> expandVariables();
/*}}}*/
	}



	/**
	 * Replaces all variable placeholders with variable values
	 * 
	 * Variable placeholder is in form of: [var::variable_name]
	 * Variable names are case sensitive. Variable may consists a-Z letters, 0-9 digits and underscore _
	 * Variable name starts with letter or underscore
	 *
	 * @access private
	 * @return boolean retuns true if any replacement was made or false if source is unchanged
	 */
	private function expandVariables(){
		/*{{{*/
		$sVariableRE = '!\[var::(?P<variable>[a-zA-Z_][a-zA-Z0-9_]*)\]!msuSU';

		preg_match_all($sVariableRE, $this -> contents, $shFound);

		$iCnt = sizeof($shFound['variable']);
		if($iCnt > 0){

			for($x = 0; $x < $iCnt; $x++){

				$sVariableName = $shFound['variable'][$x];
				$sVariableValue = $this -> vars -> getVar($sVariableName);

				$this -> contents = str_replace("[var::{$sVariableName}]", $sVariableValue, $this -> contents);
			}

		}else{
			return false;
		}

		/*}}}*/
	}



	/**
	 * Saves processed contents to target file
	 * 
	 * @access private
	 * @return void
	 */
	private function saveTargetFile(){
/*{{{*/
		if($this -> targetFileName != ''){
			file_put_contents($this -> targetFileName, $this -> contents);
		}
/*}}}*/
	}


	function getNextHeader(){}
	function getNextHeaderByName(){}
}

?>
